package logique.calculateur;

import java.util.ArrayList;
import java.util.LinkedList;

import logique.preferences.PreferencesUtilisateur;
import logique.reseau.Reseau;
import logique.reseau.Troncon;
import logique.reseau.Ville;
import controleur.Controleur;


public class Calculateur {
	
	private Controleur controleur;
	private Itineraire itineraire;
	private LinkedList<Evaluations> evaluations;
	private Algorithme algorithme;
	
	public Calculateur(Controleur controleur){
		this.controleur = controleur;
		evaluations = new LinkedList<Evaluations>();
	}
	
	public void calculeItineraire(PreferencesUtilisateur prefs){
		
		// n(n-1)/2 x (S ln(S) + A) + n! <= 11 000 000
		// n : nb étapes
		// S : nb sommets
		// A : nb arcs
		// 11 000 000 : seuil pour n = 10, S = 2000 et A = 2000*75 ~1min
		
		int n = prefs.getEtapesObligatoires().getEtapes().size();
		int s = controleur.getNbVilles();
		int a = controleur.getNbTroncons();
		
		if ( (n*(n-1)/2) * (s*Math.log(s) + a) + Optimal.factorielle(n) <= 11000000)
			algorithme = new Optimal();
		else
			algorithme = new BruteForce();
		
		this.itineraire = algorithme.calculeItineraire(this, prefs);
	}
	
	public Itineraire calculeReorientation(Ville v, PreferencesUtilisateur prefs){
		// On récupère la liste des villes de l'objectif courant de l'itineraire à la première étape obligatoire
		ArrayList<Ville> villes = new ArrayList<Ville>();
		int indexCurseur = itineraire.getCurseur().nextIndex();
		villes.add(itineraire.getObjectifCourant());
		while( itineraire.etapeSuivante() ){
			villes.add(itineraire.getObjectifCourant());
			if( prefs.getEtapesObligatoires().getEtapes().contains(itineraire.getObjectifCourant()) ){
				break;
			}
		}
		
		Dijkstra dijkstra = new Dijkstra(this);
		LinkedList<Troncon> chemin = dijkstra.calculPCC(v, villes, prefs);
		
		// On récupère la dernière ville du chemin trouvé
		Ville arrivee = v;
		for (Troncon troncon : chemin) {
			arrivee = troncon.getExtremite(arrivee);
		}
		// On récupère la suite de l'itineraire à partir de cette ville
		itineraire.setCurseur(indexCurseur);
		itineraire.setObjectifCourant(villes.get(0));
		boolean ajouterTroncon = itineraire.getObjectifCourant().equals(arrivee);
		while( itineraire.etapeSuivante() ){
			if( ajouterTroncon ){
				if( itineraire.getTronconCourant().getEvaluations() == null){
					itineraire.getTronconCourant().setEvaluations(new Evaluations(prefs, itineraire.getTronconCourant()));
					addEvaluation(itineraire.getTronconCourant().getEvaluations());
				}
				chemin.add(itineraire.getTronconCourant());
			}
			ajouterTroncon = (ajouterTroncon || (itineraire.getObjectifCourant().equals(arrivee)) ); 
		}
		
		return new Itineraire(v, chemin);
			
	}
	

	public Itineraire getItineraire() {
		return itineraire;
	}

	public Controleur getControleur() {
		return controleur;
	}

	public void setControleur(Controleur controleur) {
		this.controleur = controleur;
	}

	public void setItineraire(Itineraire itineraire) {
		this.itineraire = itineraire;
	}
	
	
	public synchronized void addEvaluation(Evaluations e) {
		try {
			if (e == null)
				throw new Exception();
			evaluations.add(e);
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}

	public synchronized LinkedList<Evaluations> getEvaluations() {
		return evaluations;
	}

	public void cleanEvaluation() {
		for (Evaluations e : evaluations) {
			e.getTroncon().setEvaluations(null);
			e.setTroncon(null);
		}
		evaluations.clear();
	}
	
	public synchronized Reseau getReseau(){
		return controleur.getReseau();
	}

	public Algorithme getAlgorithme() {
		return algorithme;
	}

	public void setAlgorithme(Algorithme algorithme) {
		this.algorithme = algorithme;
	}
}
